/*************************************************************************
	> File Name: wechat.c
	> Author: Maureen 
	> Mail: 1437867946@qq.com 
	> Created Time: Tue 12 Jan 2021 06:11:01 PM CST
 ************************************************************************/

#include "head.h"
#define MAX 10
#define MAXUSER 1000

char *conf = "./chat.conf";
int port = 0;
char name[20] = {0}, msg[512] = {0};
struct User *users;//存储用户信息的数组
int subfd;

void logout(int signum) {
    struct Msg logout_msg;
    logout_msg.type = CHAT_FIN;
    strcpy(logout_msg.from, name);
    strcpy(logout_msg.msg, "Bye!");
    for (int i = 0; i < MAXUSER; i++) {
        if (users[i].flag & FL_ONLINE) {
            send(users[i].fd, &logout_msg, sizeof(logout_msg), 0);
        }
    }    
    DBG("\n🚫 "RED" logout from wechat system..."NONE"\n");
    exit(0);
}


int main(int argc, char **argv) {
    int opt;
    //参数解析
    while ((opt = getopt(argc, argv, "p:n:m:")) != -1) {
        switch (opt) {
            case 'p':
                port = atoi(optarg);
                break;
            case 'n':
                strcpy(name, optarg);
                break;
            case 'm':
                strcpy(msg, optarg);
                break;
            default:
                fprintf(stderr, "Usage : %s -n name -p port -m msg\n", argv[0]);
                exit(1);
        }
    }
    //ctrl+c 发送SIGINT信号，退出聊天系统
    signal(SIGINT, logout);
    //若没有带选项参数，则读取配置文件中的信息
    if (!strlen(name)) strncpy(name, get_value(conf, "name"), 20);
    if (!strlen(msg)) strncpy(msg, get_value(conf, "msg"), sizeof(msg));
    if (!port) port = atoi(get_value(conf, "port"));


    //设置中文
    //setlocale(LC_ALL, "");

    DBG(BLUE"<Starting...>"NONE"name = %s, port = %d, msg = %s\n", name, port, msg);
    
    //创建套接字
    int listener;//监听套接字
    if ((listener = socket_create_udp(port)) < 0) {
        perror("socket_create_udp");
        exit(1);
    }
    DBG(BLUE"<Starting...>"NONE"listener %d ok!\n", listener);

    //并发模型：epoll, IO多路复用
    int epollfd, new_fd;
    if ((epollfd = epoll_create(1)) < 0) { //主反应堆，是前置反应堆用于接受用户连接
        perror("epoll_create");
        exit(1);
    }

    if ((subfd = epoll_create(1)) < 0) { //从反应堆
        perror("epoll_create");
        exit(1);
    }
    
    users = (struct User *)calloc(MAXUSER, sizeof(struct User));
    //epoll本质上是通过mmap做的内存映射，没有内核态与用户态的切换
    //ev就是个表单，需要填充；events记录发生的事件
    struct epoll_event ev, events[MAX]; 
    ev.events = EPOLLIN; //关注来的连接请求
    ev.data.fd = listener;//注册套接字,关联到具体的事件
    //将listener注册到主反应堆epollfd中，注册的事件是ev
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listener, &ev) < 0) {
        perror("epoll_ctl");
        exit(1);
    }

    //创建线程用于发现用户:扫描iplist，给每个IP发包
    //使用监听的socket发送消息，才是固定本机端口为8888发送消息，否则就是随机的端口 
    pthread_t client_discover_thread;
    pthread_t heart_beat_thread;
    pthread_t sub_reactor_handle_thread;
    pthread_t send_thread;
    //pthread_create(&client_discover_thread, NULL, client_discover, NULL); 
    pthread_create(&client_discover_thread, NULL, client_discover, &listener); //发现用户
    pthread_create(&heart_beat_thread, NULL, heart_beat, &listener); //发送心跳
    pthread_create(&sub_reactor_handle_thread, NULL, handle_sub_reactor, &listener); //处理从反应堆消息
    pthread_create(&send_thread, NULL, send_chat, &listener); //发送消息
    
    //此时的epollfd就是用来接收用户的连接请求
    while (1) { //针对主反应堆的操作：等待用户连接，验证用户
        //nfds个连接请求连接到了listener,要与listener建立连接
        int nfds = epoll_wait(epollfd, events, MAX, -1);
        DBG("⚙️  "RED"<Main Ractor>"NONE" : Got events...\n");

        if (nfds < 0) {
            perror("epoll_wait");
            exit(1);
        }
        for (int i = 0; i < nfds; i++) {
            struct User new_user;
            //模拟TCP的accept
            if ((new_fd = udp_accept(listener, &new_user)) < 0) {
                fprintf(stderr, RED"error in udp_accept!\n");
                continue;
            }
            //添加到从反应堆：处理聊天
            add_to_reactor(subfd, &new_user);
            DBG("⚙️  "RED"<Main Ractor>"NONE" : Adding clients to sub reactor...\n");
        }
    }

    return 0;
}

